﻿using Deferred.Library.Configuration;
using Deferred.Library.Scene;
using Deferred.Library.Scene.Visual;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Deferred.Library.Deferred
{
    /// <summary>
    /// 
    /// </summary>
    public class DeferredRenderEngine : GraphicalComponent
    {
        private DeferredBase deferredBase;

        private Effect clearEffect;
        private Effect drawFinalEffect;
        private SceneGraphVisual scene;

        private RenderTarget2D depthRT;
        private RenderTarget2D diffuseRT;
        private RenderTarget2D normalRT;
        private RenderTarget2D lightRT;

        private SceneRenderEngine sceneRenderer;
        private LightRenderEngine lightRenderer;

        private IQuadRenderer quadRenderer;
        private IOptions options;

        private EffectParameter depthRTParameter;
        private EffectParameter diffuseRTParameter;
        private EffectParameter normalRTParameter;
        private EffectParameter lightRTParameter;
        private EffectParameter viewProjParameter;

        /// <summary>
        /// 
        /// </summary>
        public SceneGraph Scene { set { scene = new SceneGraphVisual(value, deferredBase); } }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="deferredBase"></param>
        /// <param name="scene"></param>
        public DeferredRenderEngine(DeferredBase deferredBase, SceneGraph scene)
            : base(deferredBase.DeviceService)
        {
            this.scene = new SceneGraphVisual(scene, deferredBase);
            this.deferredBase = deferredBase;

            sceneRenderer = new SceneRenderEngine(deferredBase);
            lightRenderer = new LightRenderEngine(deferredBase);
        }

        /// <summary>
        /// 
        /// </summary>
        public void Initialize()
        {
            options = (IOptions)deferredBase.Services.GetService(typeof(IOptions));

            if (options != null)
            {
                options.AddOption("Render.DebugMode", false);
                options.AddOption("Render.LightDebug", false);
            }

            clearEffect = deferredBase.Content.InternalContent.Load<Effect>("ClearRenderTargets");
            drawFinalEffect = deferredBase.Content.InternalContent.Load<Effect>("DrawFinal");

            if (GraphicsDevice != null)
            {
                int width = GraphicsDevice.PresentationParameters.BackBufferWidth;
                int height = GraphicsDevice.PresentationParameters.BackBufferHeight;
                drawFinalEffect.Parameters["halfPixel"].SetValue(new Vector2(0.5f / (float)width, 0.5f / (float)height));
            }

            depthRTParameter = drawFinalEffect.Parameters["DepthRT"];
            diffuseRTParameter = drawFinalEffect.Parameters["DiffuseRT"];
            normalRTParameter = drawFinalEffect.Parameters["NormalRT"];
            lightRTParameter = drawFinalEffect.Parameters["LightRT"];
            viewProjParameter = drawFinalEffect.Parameters["ViewProj"];

            quadRenderer = (IQuadRenderer)deferredBase.Services.GetService(typeof(IQuadRenderer));

            sceneRenderer.Initialize();
            lightRenderer.Initialize();
        }

        /// <summary>
        /// 
        /// </summary>
        protected override void LoadContent()
        {
            int width = GraphicsDevice.PresentationParameters.BackBufferWidth;
            int height = GraphicsDevice.PresentationParameters.BackBufferHeight;

            depthRT = new RenderTarget2D(GraphicsDevice, width, height, 1, SurfaceFormat.Single);
            diffuseRT = new RenderTarget2D(GraphicsDevice, width, height, 1, SurfaceFormat.Color);
            normalRT = new RenderTarget2D(GraphicsDevice, width, height, 1, SurfaceFormat.Color);
            lightRT = new RenderTarget2D(GraphicsDevice, width, height, 1, SurfaceFormat.Color, RenderTargetUsage.PreserveContents);

            if (drawFinalEffect != null) drawFinalEffect.Parameters["halfPixel"].SetValue(new Vector2(0.5f / (float)width, 0.5f / (float)height));
        }

        /// <summary>
        /// 
        /// </summary>
        protected override void DestroyContent()
        {
            depthRT.Dispose();
            diffuseRT.Dispose();
            normalRT.Dispose();
            lightRT.Dispose();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="gameTime"></param>
        public void Draw(GameTime gameTime)
        {
            SetGBuffer();
            ClearRTs();
            viewProjParameter.SetValue(scene.CameraVisual.View * scene.CameraVisual.Projection);
            sceneRenderer.DrawObjects(scene.TerrainVisuals, scene.ObjectVisuals);
            SwitchGBuffer();
            lightRenderer.DrawLights(scene.Scene.AmbientLights, scene.Scene.DirectionalLights, scene.Scene.PointLights, scene.Scene.SpotLights, scene.CameraVisual, scene);
            DrawFinal();
            lightRenderer.DrawLightDebug(scene.Scene.PointLights, scene.Scene.SpotLights);
        }

        private void ClearRTs()
        {
            clearEffect.Begin();
            foreach (EffectPass pass in clearEffect.Techniques["ClearRenderTargets"].Passes)
            {
                pass.Begin();
                quadRenderer.Render(Vector2.One * -1, Vector2.One);
                pass.End();
            }
            clearEffect.End();
        }

        private void DrawFinal()
        {
            GraphicsDevice.RenderState.AlphaBlendEnable = false;
            GraphicsDevice.RenderState.DepthBufferEnable = false;
            GraphicsDevice.RenderState.AlphaTestEnable = false;

            string techniqueName = "DrawFinal";

            if (options != null && options["Render.DebugMode"]) techniqueName += "Debug";

            drawFinalEffect.CurrentTechnique = drawFinalEffect.Techniques[techniqueName];
            lightRTParameter.SetValue(lightRT.GetTexture());
            drawFinalEffect.Begin();
            foreach (EffectPass pass in drawFinalEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                quadRenderer.Render(Vector2.One * -1, Vector2.One);
                pass.End();
            }
            drawFinalEffect.End();
        }

        private void SetGBuffer()
        {
            GraphicsDevice.SetRenderTarget(0, diffuseRT);
            GraphicsDevice.SetRenderTarget(1, depthRT);
            GraphicsDevice.SetRenderTarget(2, normalRT);
        }

        private void SwitchGBuffer()
        {
            GraphicsDevice.SetRenderTarget(0, null);
            GraphicsDevice.SetRenderTarget(1, null);
            GraphicsDevice.SetRenderTarget(2, null);

            depthRTParameter.SetValue(depthRT.GetTexture());
            diffuseRTParameter.SetValue(diffuseRT.GetTexture());
            normalRTParameter.SetValue(normalRT.GetTexture());
            GraphicsDevice.SetRenderTarget(0, lightRT);
        }
    }
}
